using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;

using log4net;

using FreeBase.Fluent;

namespace FreeBase.Linq
{
	/// <summary>
	/// FreebaseExpressionVisitor converts a linq expression tree
	/// to a fluent freebase query object
	/// </summary>
	public class FreebaseExpressionVisitor
	{
		private readonly ILog logger = LogManager.GetLogger(typeof(FreebaseExpressionVisitor));
		private FBQuery _crit;

		/// <summary>
		/// Process expression converts a Linq expression
		/// to a Freebase criteria object
		/// </summary>
		/// <param name="expression"></param>
		/// <returns></returns>
		public FBQuery ProcessExpression( Expression expression )
		{
			logger.Debug( "Now processing Linq expression" );
			_crit = new FBQuery();
			
			VisitExpression( expression );
			
			logger.Debug( "Finished processing linq expression" );
			return _crit;
		}

		/// <summary>
		/// VisitExpression
		/// </summary>
		/// <param name="expression"></param>
		private void VisitExpression( Expression expression )
		{
			switch( expression.NodeType )
			{
				case ExpressionType.Add:
					logger.Debug( "Found an add expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.AddChecked:
					logger.Debug( "Found an addchecked expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.And:
					logger.Debug( "Found an and expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.AndAlso:
					logger.Debug( "Found an AndAlso expression : " + expression.ToString() );
					VisitAndAlso( ( BinaryExpression )expression );
					break;
					
				case ExpressionType.ArrayIndex:
					logger.Debug( "Found an arrayIndex expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.ArrayLength:
					logger.Debug( "Found an arrayLength expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.Call:
					logger.Debug( "Found a call expression : " + expression.ToString() );;
					VisitMethodCall( ( MethodCallExpression ) expression );
					break;
					
				case ExpressionType.Coalesce:
					logger.Debug( "Found a coalesce expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.Conditional:
					logger.Debug( "Found a conditional expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.Constant:
					logger.Debug( "Found a constant expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.Convert:
					logger.Debug( "Found a convert expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.ConvertChecked:
					logger.Debug( "Found a convertChecked expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.Divide:
					logger.Debug( "Found a divide expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.Equal:
					logger.Debug( "Found an equal expression : " + expression.ToString() );
					VisitEqual( ( BinaryExpression ) expression );
					break;
					
				case ExpressionType.ExclusiveOr:
					logger.Debug( "Found an exclusiveOr expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.GreaterThan:
					logger.Debug( "Found a greater than expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.GreaterThanOrEqual:
					logger.Debug( "Found a greaterThanOrEqual expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.Invoke:
					logger.Debug( "Found an invoke expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.Lambda:
					logger.Debug( "Found a lambda expression : " + expression.ToString() );  //HACK should use a logging tool
					VisitExpression( ( ( LambdaExpression ) expression ).Body );
					break;
					
				case ExpressionType.LeftShift:
					logger.Debug( "Found a leftshift expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.LessThan:
					logger.Debug( "Found a less than expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.LessThanOrEqual:
					logger.Debug( "Found a lessThanOrEqual expression : " + expression.ToString() );
					VisitLessThanOrEqual(( BinaryExpression ) expression );
					break;
					
				case ExpressionType.ListInit:
					logger.Debug( "Found a listInit expression : " + expression.ToString() );  //HACK should use a logging tool
					break;
					
				case ExpressionType.MemberAccess:
					logger.Debug( "Found a memberAccess expression : " + expression.ToString() + " of type " + expression.Type.ToString() );  //HACK should use a logging tool
					VisitMemberAccess( (MemberExpression)expression );
					break;
					
				case ExpressionType.MemberInit:
					logger.Debug( "Found a memberInit expression : " + expression.ToString());  //HACK should use a logging tool
					break;
					
				case ExpressionType.Modulo:
					logger.Debug( "Found a modulo expression : " + expression.ToString());  //HACK should use a logging tool
					break;
					
				case ExpressionType.Multiply:
					logger.Debug( "Found a multiply expression : " + expression.ToString());  //HACK should use a logging tool
					break;
					
				case ExpressionType.MultiplyChecked:
					logger.Debug( "Found a multiplyChecked expression : " + expression.ToString());  //HACK should use a logging tool
					break;
					
				case ExpressionType.Negate:
					logger.Debug( "Found a negate expression : " + expression.ToString());  //HACK should use a logging tool
					break;
					
				case ExpressionType.Quote:
					logger.Debug( "Found a quote expression : " + expression.ToString());  //HACK should use a logging tool
					break;
					
				default:
					/*if ( expression is MethodCallExpression )
					{
						VisitMethodCall( ( MethodCallExpression ) expression );
					}
					else if ( expression is LambdaExpression )
					{
						VisitExpression( ( ( LambdaExpression ) expression ).Body );
					}
					break;*/
					throw new NotImplementedException();
			}
		}

		private void VisitAndAlso( BinaryExpression andAlso )
		{
			VisitExpression( andAlso.Left );
			VisitExpression( andAlso.Right );
		}

		private void VisitEqual( BinaryExpression expression )
		{
			if ( ( expression.Left.NodeType == ExpressionType.MemberAccess ) )
			{
				string propertyName = ((MemberExpression ) expression.Left ).Member.Name;
				
				if ( expression.Right.NodeType == ExpressionType.Constant )
					_crit.Where( propertyName ).Is( ( String )( ( ConstantExpression )expression.Right).Value );
				else if ( expression.Right.NodeType == ExpressionType.MemberAccess )
					_crit.Where( propertyName ).Is( ( String )GetMemberValue( ( MemberExpression )expression.Right ) );
				else
					throw new NotSupportedException( "Expression type not supported for publisher: " + expression.Right.NodeType.ToString( ) );
			}
			
			// Handle Topic.Property == enumeration
			else if (  ( expression.Left is UnaryExpression ) )
			{
				string propertyName = ( ( MemberExpression )expression.Left ).Member.Name;
				if ( expression.Right.NodeType == ExpressionType.Constant )
					_crit.Where( propertyName ).Is( (String)( ( ConstantExpression )expression.Right ).Value);
				else if ( expression.Right.NodeType == ExpressionType.MemberAccess )
					_crit.Where( propertyName ).Is(( String )GetMemberValue( ( MemberExpression )expression.Right ));
				else
					throw new NotSupportedException( "Expression type not supported for book condition: " + expression.Right.NodeType.ToString( ) );
			}
		}

		private void VisitLessThanOrEqual( BinaryExpression expression )
		{
			string propertyName = ( ( MemberExpression )expression.Left ).Member.Name;
			
			// Handle Topic.Property <= xxx
			if ( ( expression.Left.NodeType == ExpressionType.MemberAccess ) )
			{
				if ( expression.Right.NodeType == ExpressionType.Constant )
					_crit.Where( propertyName ).IsLessThan( ( Decimal )( ( ConstantExpression )expression.Right ).Value );
				else if ( expression.Right.NodeType == ExpressionType.MemberAccess )
					_crit.Where( propertyName ).IsLessThan( ( Decimal )GetMemberValue( ( MemberExpression )expression.Right ) );
				else
					throw new NotSupportedException( "Expression type not supported for price: " + expression.Right.NodeType.ToString( ) );
			}
		}
		public void VisitMemberAccess( MemberExpression expression )
		{
			if( expression.Type == typeof( IList<string> ) )
				_crit.Show.ManyRecordsFor.Property( expression.Member.Name );
			else
				_crit.Show.Property( expression.Member.Name );
		}

		private void VisitMethodCall( MethodCallExpression expression )
		{
			if( expression == null )
				throw new ArgumentNullException( "expression" );
			if( expression.Method.DeclaringType != typeof( Queryable ) )
				throw new ArgumentException( "The declaring type of this expression method should be queryable" );
			
			if ( expression.Method.Name == "Where" )
			{
				logger.Debug( "Found a Where Call" ); //HACK should use logging
				
				for( int i = 0; i < expression.Arguments.Count; i++ )
				{
					if( i == 1 )
						VisitExpression( ( ( UnaryExpression )expression.Arguments[i] ).Operand );
					else
						VisitExpression( expression.Arguments[i] );
				}
			}
			else if( expression.Method.Name == "Select" )
			{
				logger.Debug( "Found a Select Call with " + expression.Arguments.Count.ToString() + " arguments" );
				
				//_crit.Show.Property(((MemberExpression)((LambdaExpression)(((UnaryExpression)expression.Arguments[1]).Operand)).Body).Member.Name);

				for( int i = 0; i < expression.Arguments.Count; i++ )
				{
					logger.Debug( "Argument " + i + " : " + expression.Arguments[i].ToString() );
					if( i == 1 )
						VisitExpression( ( ( UnaryExpression )expression.Arguments[i] ).Operand );
					else
					
						VisitExpression( expression.Arguments[i] );
				}
			}
			else
			{
				throw new NotSupportedException("Method not supported: " + expression.Method.Name);
			}
		}

		#region Helpers

		private Object GetMemberValue( MemberExpression memberExpression )
		{
			MemberInfo memberInfo;
			Object obj;

			if ( memberExpression == null )
				throw new ArgumentNullException( "memberExpression" );

			// Get object
			if ( memberExpression.Expression is ConstantExpression )
				obj = ( ( ConstantExpression)memberExpression.Expression ).Value;
			else if ( memberExpression.Expression is MemberExpression )
				obj = GetMemberValue( ( MemberExpression )memberExpression.Expression );
			else
				throw new NotSupportedException( "Expression type not supported: " + memberExpression.Expression.GetType( ).FullName );

			// Get value
			memberInfo = memberExpression.Member;
			if ( memberInfo is PropertyInfo )
			{
				PropertyInfo property = ( PropertyInfo )memberInfo;
				return property.GetValue( obj, null );
			}
			else if ( memberInfo is FieldInfo )
			{
				FieldInfo field = ( FieldInfo )memberInfo;
				return field.GetValue( obj );
			}
			else
			{
				throw new NotSupportedException( "MemberInfo type not supported: " + memberInfo.GetType( ).FullName );
			}
		}

		private Expression StripQuotes(Expression expression)
		{
			return expression; //FIXME
		}
		#endregion Helpers
	}
}
